#include "rm_test_util.h"

RC TEST_RM_0(const std::string &tableName) {
    // Functions Tested
    // 1. getAttributes **
    std::cout << std::endl << "***** In RM Test Case 0 *****" << std::endl;

    // GetAttributes
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    for (unsigned i = 0; i < (unsigned) attrs.size(); i++) {
        std::cout << (i + 1) << ". Attr Name: " << attrs[i].name << " Type: " << (AttrType) attrs[i].type << " Len: "
                  << attrs[i].length << std::endl;
    }

    std::cout << std::endl << "***** RM Test Case 0 finished. The result will be examined. *****" << std::endl;

    return success;
}

RC TEST_RM_1(const std::string &tableName, const int nameLength, const std::string &name, const int age,
             const float height, const int salary) {
    // Functions tested
    // 1. Insert Tuple **
    // 2. Read Tuple **
    // NOTE: "**" signifies the new functions being tested in this test case.
    std::cout << std::endl << "***** In RM Test Case 1 *****" << std::endl;

    RID rid;
    unsigned tupleSize = 0;
    void *tuple = malloc(200);
    void *returnedData = malloc(200);

    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    // Initialize a NULL field indicator
    unsigned nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicator, 0, nullAttributesIndicatorActualSize);

    // Insert a tuple into a table
    prepareTuple(attrs.size(), nullsIndicator, nameLength, name, age, height, salary, tuple, &tupleSize);
    std::cout << "The tuple to be inserted:" << std::endl;
    rm.printTuple(attrs, tuple);
    std::cout << std::endl;

    rc = rm.insertTuple(tableName, tuple, rid);
    assert(rc == success && "RelationManager::insertTuple() should not fail.");

    // Given the rid, read the tuple from table
    rc = rm.readTuple(tableName, rid, returnedData);
    assert(rc == success && "RelationManager::readTuple() should not fail.");

    std::cout << "The returned tuple:" << std::endl;
    rm.printTuple(attrs, returnedData);
    std::cout << std::endl;

    // Compare whether the two memory blocks are the same
    if (memcmp(tuple, returnedData, tupleSize) == 0) {
        std::cout << "**** RM Test Case 1 finished. The result will be examined. *****" << std::endl << std::endl;
        free(tuple);
        free(returnedData);
        free(nullsIndicator);
        return success;
    } else {
        std::cout << "**** [FAIL] RM Test Case 1 failed *****" << std::endl << std::endl;
        free(tuple);
        free(returnedData);
        free(nullsIndicator);
        return -1;
    }

}

RC TEST_RM_2(const std::string &tableName, const int nameLength, const std::string &name, const int age,
            const float height, const int salary) {
    // Functions Tested
    // 1. Insert tuple
    // 2. Delete Tuple **
    // 3. Read Tuple
    std::cout << std::endl << "***** In RM Test Case 2 *****" << std::endl;

    RID rid;
    unsigned tupleSize = 0;
    void *tuple = malloc(200);
    void *returnedData = malloc(200);

    // Test Insert the Tuple
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    int nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicator, 0, nullAttributesIndicatorActualSize);

    prepareTuple(attrs.size(), nullsIndicator, nameLength, name, age, height, salary, tuple, &tupleSize);
    std::cout << "The tuple to be inserted:" << std::endl;
    rm.printTuple(attrs, tuple);
    std::cout << std::endl;
    rc = rm.insertTuple(tableName, tuple, rid);
    assert(rc == success && "RelationManager::insertTuple() should not fail.");

    // Delete the tuple
    rc = rm.deleteTuple(tableName, rid);
    assert(rc == success && "RelationManager::deleteTuple() should not fail.");

    // Read Tuple after deleting it - should fail
    memset(returnedData, 0, 200);
    rc = rm.readTuple(tableName, rid, returnedData);
    assert(rc != success && "Reading a deleted tuple should fail.");

    // Compare the two memory blocks to see whether they are different
    if (memcmp(tuple, returnedData, tupleSize) != 0) {
        std::cout << "***** RM Test Case 2 finished. The result will be examined. *****" << std::endl << std::endl;
        free(tuple);
        free(returnedData);
        free(nullsIndicator);
        return success;
    } else {
        std::cout << "***** [FAIL] RM Test case 2 failed *****" << std::endl << std::endl;
        free(tuple);
        free(returnedData);
        free(nullsIndicator);
        return -1;
    }

}

RC TEST_RM_3(const std::string &tableName, const int nameLength, const std::string &name, const int age,
             const float height, const int salary) {
    // Functions Tested
    // 1. Insert Tuple
    // 2. Update Tuple **
    // 3. Read Tuple
    std::cout << std::endl << "***** In RM Test Case 3****" << std::endl;

    RID rid;
    unsigned tupleSize = 0;
    unsigned updatedTupleSize = 0;
    void *tuple = malloc(200);
    void *updatedTuple = malloc(200);
    void *returnedData = malloc(200);

    // Test Insert the Tuple
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    unsigned nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicator, 0, nullAttributesIndicatorActualSize);

    prepareTuple(attrs.size(), nullsIndicator, nameLength, name, age, height, salary, tuple, &tupleSize);
    rc = rm.insertTuple(tableName, tuple, rid);
    assert(rc == success && "RelationManager::insertTuple() should not fail.");
    std::cout << "Original RID:  " << rid.pageNum << " " << rid.slotNum << std::endl;

    // Test Update Tuple
    prepareTuple(attrs.size(), nullsIndicator, 7, "Barbara", age, height, 12000, updatedTuple, &updatedTupleSize);
    rc = rm.updateTuple(tableName, updatedTuple, rid);
    assert(rc == success && "RelationManager::updateTuple() should not fail.");

    // Test Read Tuple
    rc = rm.readTuple(tableName, rid, returnedData);
    assert(rc == success && "RelationManager::readTuple() should not fail.");

    // Print the tuples
    std::cout << "Inserted Data:" << std::endl;
    rm.printTuple(attrs, tuple);
    std::cout << std::endl;

    std::cout << "Updated data:" << std::endl;
    rm.printTuple(attrs, updatedTuple);
    std::cout << std::endl;

    std::cout << "Returned Data:" << std::endl;
    rm.printTuple(attrs, returnedData);
    std::cout << std::endl;

    if (memcmp(updatedTuple, returnedData, updatedTupleSize) == 0) {
        std::cout << "***** RM Test Case 3 Finished. The result will be examined. *****" << std::endl << std::endl;
        free(tuple);
        free(updatedTuple);
        free(returnedData);
        free(nullsIndicator);
        return 0;
    } else {
        std::cout << "***** [FAIL] RM Test case 3 Failed *****" << std::endl << std::endl;
        free(tuple);
        free(updatedTuple);
        free(returnedData);
        free(nullsIndicator);
        return -1;
    }

}

RC TEST_RM_4(const std::string &tableName, const int nameLength, const std::string &name, const int age,
             const float height, const int salary) {
    // Functions Tested
    // 1. Insert tuple
    // 2. Read Attributes **
    std::cout << std::endl << "***** In RM Test Case 4 *****" << std::endl;

    RID rid;
    unsigned tupleSize = 0;
    void *tuple = malloc(200);
    void *returnedData = malloc(200);

    // Test Insert the Tuple
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    unsigned nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicator, 0, nullAttributesIndicatorActualSize);

    prepareTuple(attrs.size(), nullsIndicator, nameLength, name, age, height, salary, tuple, &tupleSize);
    rc = rm.insertTuple(tableName, tuple, rid);
    assert(rc == success && "RelationManager::insertTuple() should not fail.");

    // Test Read Attribute
    rc = rm.readAttribute(tableName, rid, "Salary", returnedData);
    assert(rc == success && "RelationManager::readAttribute() should not fail.");

    int salaryBack = *(int *) ((char *) returnedData + nullAttributesIndicatorActualSize);

    std::cout << "Salary: " << salary << " Returned Salary: " << salaryBack << std::endl;
    if (memcmp((char *) returnedData + nullAttributesIndicatorActualSize,
               (char *) tuple + 19 + nullAttributesIndicatorActualSize, 4) == 0) {
        std::cout << "***** RM Test case 4 Finished. The result will be examined. *****" << std::endl << std::endl;
        free(tuple);
        free(returnedData);
        free(nullsIndicator);
        return success;
    } else {
        std::cout << "***** [FAIL] RM Test Case 4 Failed. *****" << std::endl << std::endl;
        free(tuple);
        free(returnedData);
        free(nullsIndicator);
        return -1;
    }

}

RC TEST_RM_5(const std::string &tableName, const int nameLength, const std::string &name, const int age,
             const float height,
             const int salary) {
    // Functions Tested
    // 0. Insert tuple;
    // 1. Read Tuple
    // 2. Delete Table **
    // 3. Read Tuple
    // 4. Insert Tuple
    std::cout << std::endl << "***** In RM Test Case 5 *****" << std::endl;

    RID rid;
    unsigned tupleSize = 0;
    void *tuple = malloc(200);
    void *returnedData = malloc(200);
    void *returnedData1 = malloc(200);

    // Test Insert Tuple
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    int nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicator, 0, nullAttributesIndicatorActualSize);

    prepareTuple(attrs.size(), nullsIndicator, nameLength, name, age, height, salary, tuple, &tupleSize);
    rc = rm.insertTuple(tableName, tuple, rid);
    assert(rc == success && "RelationManager::insertTuple() should not fail.");

    // Test Read Tuple
    rc = rm.readTuple(tableName, rid, returnedData);
    assert(rc == success && "RelationManager::readTuple() should not fail.");

    // Test Delete Table
    rc = rm.deleteTable(tableName);
    assert(rc == success && "RelationManager::deleteTable() should not fail.");

    // Reading a tuple on a deleted table
    memset((char *) returnedData1, 0, 200);
    rc = rm.readTuple(tableName, rid, returnedData1);
    assert(rc != success && "RelationManager::readTuple() on a deleted table should fail.");

    // Inserting a tuple on a deleted table
    rc = rm.insertTuple(tableName, tuple, rid);
    assert(rc != success && "RelationManager::insertTuple() on a deleted table should fail.");

    if (memcmp(returnedData, returnedData1, tupleSize) != 0) {
        std::cout << "***** Test Case 5 Finished. The result will be examined. *****" << std::endl << std::endl;
        free(tuple);
        free(returnedData);
        free(returnedData1);
        free(nullsIndicator);
        return success;
    } else {
        std::cout << "***** [FAIL] Test Case 5 Failed *****" << std::endl << std::endl;
        free(tuple);
        free(returnedData);
        free(returnedData1);
        free(nullsIndicator);
        return -1;
    }
}

RC TEST_RM_6(const std::string &tableName) {
    // Functions Tested
    // 1. Simple scan **
    std::cout << std::endl << "***** In RM Test Case 6 *****" << std::endl;

    RID rid;
    unsigned tupleSize = 0;
    int numTuples = 100;
    void *tuple;
    void *returnedData = malloc(200);

    // Test Insert Tuple
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    unsigned nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicator, 0, nullAttributesIndicatorActualSize);

    RID rids[numTuples];
    std::set<int> ages;
    for (int i = 0; i < numTuples; i++) {
        tuple = malloc(200);

        // Insert Tuple
        float height = (float) i;
        int age = 20 + i;
        prepareTuple(attrs.size(), nullsIndicator, 6, "Tester", age, height, age * 10, tuple, &tupleSize);
        ages.insert(age);
        rc = rm.insertTuple(tableName, tuple, rid);
        assert(rc == success && "RelationManager::insertTuple() should not fail.");

        rids[i] = rid;
        free(tuple);
    }

    // Set up the iterator
    RM_ScanIterator rmsi;
    std::string attr = "Age";
    std::vector<std::string> attributes;
    attributes.push_back(attr);
    rc = rm.scan(tableName, "", NO_OP, NULL, attributes, rmsi);
    assert(rc == success && "RelationManager::scan() should not fail.");

    nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attributes.size());
    while (rmsi.getNextTuple(rid, returnedData) != RM_EOF) {
        if (ages.find(*(int *) ((char *) returnedData + nullAttributesIndicatorActualSize)) == ages.end()) {
            std::cout << "***** [FAIL] Test Case 6 Failed *****" << std::endl << std::endl;
            rmsi.close();
            free(returnedData);
            free(nullsIndicator);
            return -1;
        }
    }
    rmsi.close();

    free(returnedData);
    free(nullsIndicator);
    std::cout << "***** Test Case 6 Finished. The result will be examined. *****" << std::endl << std::endl;
    return 0;
}

RC TEST_RM_7(const std::string &tableName) {
    // Functions Tested
    // 1. Simple scan **
    // 2. Delete the given table
    std::cout << std::endl << "***** In RM Test Case 7 *****" << std::endl;

    RID rid;
    int numTuples = 100;
    void *returnedData = malloc(200);

    std::set<int> ages;
    RC rc = 0;
    for (int i = 0; i < numTuples; i++) {
        int age = 20 + i;
        ages.insert(age);
    }

    // Set up the iterator
    RM_ScanIterator rmsi;
    std::string attr = "Age";
    std::vector<std::string> attributes;
    attributes.push_back(attr);
    rc = rm.scan(tableName, "", NO_OP, NULL, attributes, rmsi);
    assert(rc == success && "RelationManager::scan() should not fail.");
    int ageReturned = 0;

    while (rmsi.getNextTuple(rid, returnedData) != RM_EOF) {
        //std::cout << "Returned Age: " << *(int *)((char *)returnedData+1) <<std::endl;
        ageReturned = *(int *) ((char *) returnedData + 1);
        if (ages.find(ageReturned) == ages.end()) {
            std::cout << "***** [FAIL] Test Case 7 Failed *****" << std::endl << std::endl;
            rmsi.close();
            free(returnedData);
            return -1;
        }
    }
    rmsi.close();

    // Delete a Table
    rc = rm.deleteTable(tableName);
    assert(rc == success && "RelationManager::deleteTable() should not fail.");

    free(returnedData);
    std::cout << "***** Test Case 7 Finished. The result will be examined. *****" << std::endl << std::endl;
    return success;
}

RC TEST_RM_8(const std::string &tableName, std::vector<RID> &rids, std::vector<int> &sizes) {
    // Functions Tested for large tables:
    // 1. getAttributes
    // 2. insert tuple
    std::cout << std::endl << "***** In RM Test Case 8 *****" << std::endl;

    RID rid;
    void *tuple = malloc(4000);
    int numTuples = 2000;

    // GetAttributes
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    int nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicator, 0, nullAttributesIndicatorActualSize);

    // Insert 2000 tuples into table
    for (int i = 0; i < numTuples; i++) {
        // Test insert Tuple
        int size = 0;
        memset(tuple, 0, 2000);
        prepareLargeTuple(attrs.size(), nullsIndicator, i, tuple, &size);

        rc = rm.insertTuple(tableName, tuple, rid);
        assert(rc == success && "RelationManager::insertTuple() should not fail.");

        rids.push_back(rid);
        sizes.push_back(size);

        if ((i+1) % 100 == 0) {
            cout << i + 1 << "tuples are inserted " << endl;
        }
    }

    free(tuple);
    free(nullsIndicator);

    writeRIDsToDisk(rids);
    writeSizesToDisk(sizes);

    std::cout << "***** Test Case 8 Finished. The result will be examined. *****" << std::endl << std::endl;

    return success;
}

RC TEST_RM_09(const std::string &tableName, std::vector<RID> &rids, std::vector<int> &sizes) {
    // Functions Tested for large tables:
    // 1. read tuple
    std::cout << "***** In RM Test case 9 *****" << std::endl;

    int size = 0;
    int numTuples = 2000;
    void *tuple = malloc(4000);
    void *returnedData = malloc(4000);

    // read the saved rids and the sizes of records
    readRIDsFromDisk(rids, numTuples);
    readSizesFromDisk(sizes, numTuples);

    // GetAttributes
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    int nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicator, 0, nullAttributesIndicatorActualSize);

    for (int i = 0; i < numTuples; i++) {
        memset(tuple, 0, 4000);
        memset(returnedData, 0, 4000);
        rc = rm.readTuple(tableName, rids[i], returnedData);
        assert(rc == success && "RelationManager::readTuple() should not fail.");

        size = 0;
        prepareLargeTuple(attrs.size(), nullsIndicator, i, tuple, &size);
        if (memcmp(returnedData, tuple, sizes[i]) != 0) {
            std::cout << "***** [FAIL] Test Case 9 Failed *****" << std::endl << std::endl;
            free(tuple);
            free(returnedData);
            free(nullsIndicator);
            return -1;
        }
    }

    free(tuple);
    free(returnedData);
    free(nullsIndicator);

    std::cout << "***** Test Case 9 Finished. The result will be examined. *****" << std::endl << std::endl;

    return success;
}

RC TEST_RM_10(const std::string &tableName, std::vector<RID> &rids, std::vector<int> &sizes) {
    // Functions Tested for large tables:
    // 1. update tuple
    // 2. read tuple
    std::cout << std::endl << "***** In RM Test case 10 *****" << std::endl;

    int numTuples = 2000;
    void *tuple = malloc(4000);
    void *returnedData = malloc(4000);

    readRIDsFromDisk(rids, numTuples);
    readSizesFromDisk(sizes, numTuples);

    // GetAttributes
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    int nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());
    auto nullsIndicator = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicator, 0, nullAttributesIndicatorActualSize);

    // Update the first 1000 tuples
    int size = 0;
    for (int i = 0; i < 1000; i++) {
        memset(tuple, 0, 4000);
        RID rid = rids[i];

        prepareLargeTuple(attrs.size(), nullsIndicator, i + 10, tuple, &size);
        rc = rm.updateTuple(tableName, tuple, rid);
        assert(rc == success && "RelationManager::updateTuple() should not fail.");

        sizes[i] = size;
        rids[i] = rid;
    }

    // Read the updated records and check the integrity
    for (int i = 0; i < 1000; i++) {
        memset(tuple, 0, 4000);
        memset(returnedData, 0, 4000);
        prepareLargeTuple(attrs.size(), nullsIndicator, i + 10, tuple, &size);
        rc = rm.readTuple(tableName, rids[i], returnedData);
        assert(rc == success && "RelationManager::readTuple() should not fail.");

        if (memcmp(returnedData, tuple, sizes[i]) != 0) {
            std::cout << "***** [FAIL] Test Case 10 Failed *****" << std::endl << std::endl;
            free(tuple);
            free(returnedData);
            free(nullsIndicator);
            return -1;
        }
    }

    free(tuple);
    free(returnedData);
    free(nullsIndicator);

    std::cout << "***** Test Case 10 Finished. The result will be examined. *****" << std::endl << std::endl;

    return success;

}

RC TEST_RM_11(const std::string &tableName, std::vector<RID> &rids) {
    // Functions Tested for large tables:
    // 1. delete tuple
    // 2. read tuple
    std::cout << std::endl << "***** In RM Test Case 11 *****" << std::endl;

    int numTuples = 2000;
    RC rc = 0;
    void *returnedData = malloc(4000);

    readRIDsFromDisk(rids, numTuples);

    // Delete the first 1000 tuples
    for (int i = 0; i < 1000; i++) {
        rc = rm.deleteTuple(tableName, rids[i]);
        assert(rc == success && "RelationManager::deleteTuple() should not fail.");
    }

    // Try to read the first 1000 deleted tuples
    for (int i = 0; i < 1000; i++) {
        rc = rm.readTuple(tableName, rids[i], returnedData);
        assert(rc != success && "RelationManager::readTuple() on a deleted tuple should fail.");
    }

    for (int i = 1000; i < 2000; i++) {
        rc = rm.readTuple(tableName, rids[i], returnedData);
        assert(rc == success && "RelationManager::readTuple() should not fail.");
    }
    std::cout << "***** Test Case 11 Finished. The result will be examined. *****" << std::endl << std::endl;

    free(returnedData);

    return success;
}

RC TEST_RM_12(const std::string &tableName) {
    // Functions Tested for large tables
    // 1. scan
    std::cout << std::endl << "***** In RM Test case 12 *****" << std::endl;

    RM_ScanIterator rmsi;
    std::vector<std::string> attrs;
    attrs.emplace_back("attr5");
    attrs.emplace_back("attr12");
    attrs.emplace_back("attr28");

    RC rc = rm.scan(tableName, "", NO_OP, NULL, attrs, rmsi);
    assert(rc == success && "RelationManager::scan() should not fail.");

    RID rid;
    int j = 0;
    void *returnedData = malloc(4000);

    int nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());

    while (rmsi.getNextTuple(rid, returnedData) != RM_EOF) {
        if (j % 200 == 0) {
            int offset = 0;

            std::cout << "Real Value: " << *(float *) ((char *) returnedData + nullAttributesIndicatorActualSize)
                      << std::endl;
            offset += 4;

            int size = *(int *) ((char *) returnedData + offset + nullAttributesIndicatorActualSize);
            std::cout << "Varchar size: " << size << std::endl;
            offset += 4;

            char *buffer = (char *) malloc(size + 1);
            memcpy(buffer, (char *) returnedData + offset + nullAttributesIndicatorActualSize, size);
            buffer[size] = 0;
            offset += size;

            std::cout << "VarChar Value: " << buffer << std::endl;

            std::cout << "Integer Value: "
                      << *(int *) ((char *) returnedData + offset + nullAttributesIndicatorActualSize) << std::endl
                      << std::endl;
            offset += 4;

            free(buffer);
        }
        j++;
        memset(returnedData, 0, 4000);
    }
    rmsi.close();
    std::cout << "Total number of tuples: " << j << std::endl << std::endl;
    if (j > 1000) {
        std::cout << "***** [FAIL] Test Case 12 Failed *****" << std::endl << std::endl;
        free(returnedData);
        return -1;
    }

    std::cout << "***** Test Case 12 Finished. The result will be examined. *****" << std::endl << std::endl;
    free(returnedData);

    return success;
}

RC TEST_RM_13(const std::string &tableName) {
    // Functions Tested:
    // 1. Conditional scan
    std::cout << std::endl << "***** In RM Test Case 13 *****" << std::endl;

    RID rid;
    unsigned tupleSize = 0;
    int numTuples = 500;
    void *tuple;
    void *returnedData = malloc(200);
    int ageVal = 25;
    int age = 0;

    RID rids[numTuples];
    std::vector<char *> tuples;

    // GetAttributes
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    unsigned nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicator, 0, nullAttributesIndicatorActualSize);

    for (int i = 0; i < numTuples; i++) {
        tuple = malloc(100);

        // Insert Tuple
        auto height = (float) i;

        age = (rand() % 10) + 23;

        prepareTuple(attrs.size(), nullsIndicator, 6, "Tester", age, height, 123, tuple, &tupleSize);
        rc = rm.insertTuple(tableName, tuple, rid);
        assert(rc == success && "RelationManager::insertTuple() should not fail.");

        rids[i] = rid;
        free(tuple);
    }

    // Set up the iterator
    RM_ScanIterator rmsi;
    std::string attr = "Age";
    std::vector<std::string> attributes;
    attributes.push_back(attr);
    rc = rm.scan(tableName, attr, GT_OP, &ageVal, attributes, rmsi);
    assert(rc == success && "RelationManager::scan() should not fail.");

    while (rmsi.getNextTuple(rid, returnedData) != RM_EOF) {
        age = *(int *) ((char *) returnedData + 1);
        if (age <= ageVal) {
            std::cout << "Returned value from a scan is not correct." << std::endl;
            std::cout << "***** [FAIL] Test Case 13 Failed *****" << std::endl << std::endl;
            rmsi.close();
            free(returnedData);
            free(nullsIndicator);
            return -1;
        }
    }
    rmsi.close();
    free(returnedData);
    free(nullsIndicator);

    rc = rm.deleteTable("tbl_b_employee4");

    std::cout << "***** Test Case 13 Finished. The result will be examined. *****" << std::endl << std::endl;

    return success;
}

RC TEST_RM_13b(const std::string &tableName) {
    // Functions Tested:
    // 1. Conditional scan - including NULL values
    std::cout << std::endl << "***** In RM Test Case 13B *****" << std::endl;

    RID rid;
    unsigned tupleSize = 0;
    int numTuples = 500;
    void *tuple;
    void *returnedData = malloc(200);
    int ageVal = 25;
    int age = 0;

    RID rids[numTuples];
    std::vector<char *> tuples;
    std::string tupleName;
    char *suffix = (char *) malloc(10);

    bool nullBit = false;

    // GetAttributes
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    int nullAttributesIndicatorActualSize = getActualByteForNullsIndicator(attrs.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicator, 0, nullAttributesIndicatorActualSize);

    auto *nullsIndicatorWithNull = (unsigned char *) malloc(nullAttributesIndicatorActualSize);
    memset(nullsIndicatorWithNull, 0, nullAttributesIndicatorActualSize);

    // age field : NULL
    nullsIndicatorWithNull[0] = 64; // 01000000

    for (int i = 0; i < numTuples; i++) {
        tuple = malloc(100);

        // Insert Tuple
        auto height = (float) i;

        age = (rand() % 20) + 15;

        sprintf(suffix, "%d", i);

        if (i % 10 == 0) {
            tupleName = "TesterNull";
            tupleName += suffix;
            prepareTuple(attrs.size(), nullsIndicatorWithNull, tupleName.length(), tupleName, 0, height, 456, tuple,
                         &tupleSize);
        } else {
            tupleName = "Tester";
            tupleName += suffix;
            prepareTuple(attrs.size(), nullsIndicator, tupleName.length(), tupleName, age, height, 123, tuple,
                         &tupleSize);
        }
        rc = rm.insertTuple(tableName, tuple, rid);
        assert(rc == success && "RelationManager::insertTuple() should not fail.");

        rids[i] = rid;
        free(tuple);
    }

    // Set up the iterator
    RM_ScanIterator rmsi;
    std::string attr = "Age";
    std::vector<std::string> attributes;
    attributes.push_back(attr);
    rc = rm.scan(tableName, attr, GT_OP, &ageVal, attributes, rmsi);
    assert(rc == success && "RelationManager::scan() should not fail.");

    while (rmsi.getNextTuple(rid, returnedData) != RM_EOF) {
        // Check the first bit of the returned data since we only return one attribute in this test case
        // However, the age with NULL should not be returned since the condition NULL > 25 can't hold.
        // All comparison operations with NULL should return FALSE
        // (e.g., NULL > 25, NULL >= 25, NULL <= 25, NULL < 25, NULL == 25, NULL != 25: ALL FALSE)
        nullBit = *(unsigned char *) ((char *) returnedData) & ((unsigned) 1 << (unsigned) 7);
        if (!nullBit) {
            age = *(int *) ((char *) returnedData + 1);
            if (age <= ageVal) {
                // Comparison didn't work in this case
                std::cout << "Returned value from a scan is not correct: returned Age <= 25." << std::endl;
                std::cout << "***** [FAIL] Test Case 13B Failed *****" << std::endl << std::endl;
                rmsi.close();
                free(returnedData);
                free(suffix);
                free(nullsIndicator);
                free(nullsIndicatorWithNull);
                return -1;
            }
        } else {
            // Age with NULL value should not be returned.
            std::cout << "Returned value from a scan is not correct. NULL returned." << std::endl;
            std::cout << "***** [FAIL] Test Case 13B Failed *****" << std::endl << std::endl;
            rmsi.close();
            free(returnedData);
            free(suffix);
            free(nullsIndicator);
            free(nullsIndicatorWithNull);
            return -1;
        }
    }
    rmsi.close();
    free(returnedData);
    free(suffix);
    free(nullsIndicator);
    free(nullsIndicatorWithNull);

    rc = rm.deleteTable("tbl_b_employee5");

    std::cout << "Test Case 13B Finished. The result will be examined. *****" << std::endl << std::endl;

    return success;
}

RC TEST_RM_14(const std::string &tableName) {
    // Functions Tested:
    // 1. System Catalog Implementation - Tables table
    std::cout << std::endl << "***** In RM Test Case 14 *****" << std::endl;

    // Get Catalog Attributes
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    // There should be at least three attributes: table-id, table-name, file-name
    if (attrs.size() < 3) {
        std::cout << "Your system catalog schema is not correct." << std::endl;
        std::cout << "***** [FAIL] Test Case 14 failed *****" << std::endl;
        return -1;
    } else if (attrs[0].name != "table-id" || attrs[1].name != "table-name" || attrs[2].name != "file-name") {
        std::cout << "Your system catalog schema is not correct." << std::endl;
        std::cout << "***** [FAIL] Test Case 14 failed *****" << std::endl;
        return -1;
    }

    RID rid;
    void *returnedData = malloc(1000);

    // Set up the iterator
    RM_ScanIterator rmsi;
    std::vector<std::string> projected_attrs;
    projected_attrs.reserve(attrs.size());
    for (Attribute &attr : attrs) {
        projected_attrs.push_back(attr.name);
    }

    rc = rm.scan(tableName, "", NO_OP, NULL, projected_attrs, rmsi);
    assert(rc == success && "RelationManager::scan() should not fail.");

    int count = 0;
    while (rmsi.getNextTuple(rid, returnedData) != RM_EOF) {
        // We will manually check the returned tuples to see whether your implementation is correct or not.
        rm.printTuple(attrs, returnedData);
        count++;
    }
    rmsi.close();

    // There should be at least two rows - one for Tables and one for Columns
    if (count < 2) {
        std::cout << "Your system catalog schema is not correct." << std::endl;
        std::cout << "***** [FAIL] Test Case 14 failed" << std::endl;
        free(returnedData);
        return -1;
    }

    // Deleting the catalog should fail.
    rc = rm.deleteTable(tableName);
    assert(rc != success && "RelationManager::deleteTable() on the system catalog table should fail.");

    free(returnedData);
    std::cout << "***** Test Case 14 Finished. The result will be examined. *****" << std::endl;
    return 0;
}

RC TEST_RM_15(const std::string &tableName) {
    // Functions Tested:
    // 1. System Catalog Implementation - Columns table
    std::cout << std::endl << "***** In RM Test Case 15 *****" << std::endl;

    // Get Catalog Attributes
    std::vector<Attribute> attrs;
    RC rc = rm.getAttributes(tableName, attrs);
    assert(rc == success && "RelationManager::getAttributes() should not fail.");

    // There should be at least five attributes: table-id, column-name, column-type, column-length, column-position
    if (attrs.size() < 5) {
        std::cout << "Your system catalog schema is not correct." << std::endl;
        std::cout << "***** [FAIL] Test Case 15 failed *****" << std::endl;
        return -1;
    } else if (attrs[0].name != "table-id" || attrs[1].name != "column-name" ||
               attrs[2].name != "column-type" || attrs[3].name != "column-length" ||
               attrs[4].name != "column-position") {
        std::cout << "Your system catalog schema is not correct." << std::endl;
        std::cout << "***** [FAIL] Test Case 15 failed *****" << std::endl;
        return -1;
    }

    RID rid;
    void *returnedData = malloc(200);

    // Set up the iterator
    RM_ScanIterator rmsi;
    std::vector<std::string> projected_attrs;
    for (Attribute &attr : attrs) {
        projected_attrs.push_back(attr.name);
    }

    rc = rm.scan(tableName, "", NO_OP, NULL, projected_attrs, rmsi);
    assert(rc == success && "RelationManager::scan() should not fail.");

    int count = 0;
    while (rmsi.getNextTuple(rid, returnedData) != RM_EOF) {
        // We will manually check the returned tuples to see whether your implementation is correct or not.
        rm.printTuple(attrs, returnedData);
        count++;
    }
    rmsi.close();

    // There should be at least eight rows - three for Tables and five for Columns
    if (count < 8) {
        std::cout << "Your system catalog schema is not correct." << std::endl;
        std::cout << "***** [FAIL] Test Case 15 failed" << std::endl;
        free(returnedData);
        return -1;
    }

    // Deleting the catalog should fail.
    rc = rm.deleteTable(tableName);
    assert(rc != success && "RelationManager::deleteTable() on the system catalog table should fail.");

    free(returnedData);
    std::cout << "***** Test Case 15 Finished. The result will be examined. *****" << std::endl;
    return 0;
}

int main() {
    RC rc;

    rc = TEST_RM_0("tbl_employee");
    if (rc != 0) return rc;

    rc = TEST_RM_1("tbl_employee", 14, "Peter Anteater", 27, 6.2, 10000);
    if (rc != 0) return rc;

    rc = TEST_RM_2("tbl_employee", 5, "Peter", 23, 5.11, 12000);
    if (rc != 0) return rc;

    rc = TEST_RM_3("tbl_employee", 4, "Paul", 28, 6.5, 6000);
    if (rc != 0) return rc;

    rc = TEST_RM_4("tbl_employee", 7, "Hoffman", 31, 5.8, 9999);
    if (rc != 0) return rc;

    rc = TEST_RM_5("tbl_employee", 6, "Martin", 29, 193.6, 20000);
    if (rc != 0) return rc;

    rc = TEST_RM_6("tbl_employee3");
    if (rc != 0) return rc;

    rc = TEST_RM_7("tbl_employee3");
    if (rc != 0) return rc;


    std::vector<RID> rids;
    std::vector<int> sizes;

    // Insert Tuple
    rc = TEST_RM_8("tbl_employee4", rids, sizes);
    if (rc != 0) return rc;

    rids.clear();
    sizes.clear();

    // Read Tuple
    rc = TEST_RM_09("tbl_employee4", rids, sizes);
    if (rc != 0) return rc;

    rids.clear();
    sizes.clear();

    // Update Tuple
    rc = TEST_RM_10("tbl_employee4", rids, sizes);
    if (rc != 0) return rc;

    rids.clear();
    sizes.clear();

    // Delete Tuple
    rc = TEST_RM_11("tbl_employee4", rids);
    if (rc != 0) return rc;

    rc = TEST_RM_12("tbl_employee4");
    if (rc != 0) return rc;

    createTable("tbl_b_employee4");
    rc = TEST_RM_13("tbl_b_employee4");
    if (rc != 0) return rc;

    createTable("tbl_b_employee5");
    rc = TEST_RM_13b("tbl_b_employee5");
    if (rc != 0) return rc;

    // NOTE: your Tables table must be called "Tables"
    std::string catalog_table_name = "Tables";

    // Test Catalog Information
    rc = TEST_RM_14(catalog_table_name);
    if (rc != 0) return rc;

    // NOTE: your Columns table must be called "Columns"
    std::string column_table_name = "Columns";

    // Test Catalog Information
    rc = TEST_RM_15(column_table_name);
    if (rc != 0) return rc;
    return 0;
}